﻿using LittleBee.Cells;
using LittleBee.JsonRpc;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Data;

namespace LittleBee.AppServ.JsonRpcInterface
{
	[JsonRpcVersion("0.1.0")]
	public class AppServJsonRpcInterface
	{
		/// <summary>
		/// 客户端启动时，首先会调用此接口获取cell相关总览信息。返回的信息主要包括
		/// 1. cell type / cell id list, 及各cell type对应的cell id list
		/// params: {}
		/// result: [{"cell_type":"xxx1", "cell_name": "xxx1", "cell_id":1, "apps":[{"app_id:11, "app_name":"xxx11"}]},
		///			{"cell_type":"xxx2", "cell_name": "xxx2", "cell_id":2, "apps":[{"app_id:22, "app_name":"xxx22"}]},
		///			...
		/// </summary>
		/// <param name="jtParam"></param>
		/// <param name="jtResult"></param>
		/// <returns></returns>
		[JsonRpcMethod]
		[JsonRpcMethodExample(@"{}")]
		static public bool GetCellSummaryInfo(JToken jtParam, out JToken? jtResult)
		{
			bool blRet = CellService.Instance.GetCellSummaryInfo(out DataTable? dtCellSummary);

			JArray jaResult = new();

			if (blRet == true)
			{
				foreach (DataRow cell_row in dtCellSummary!.Rows)
				{
					Int64 cell_id = cell_row.Field<Int64>("cell_id");

					JArray? jtCell = (jaResult.SelectToken($"$[?(@.cell_id=={cell_id})].apps") as JArray);
					if (jtCell == null)
					{
						var joCell = new JObject
						{
							["cell_type"] = cell_row.Field<string>("type"),
							["cell_name"] = cell_row.Field<string>("name"),
							["cell_id"] = cell_row.Field<Int64>("cell_id"),
						};

						if (cell_row.IsNull("app_id"))
						{
							joCell["apps"] = new JArray();
						}
						else
						{
							var joApp = new JObject
							{
								["app_id"] = cell_row.Field<Int64>("app_id"),
								["app_name"] = cell_row.Field<string>("app_name"),
							};
							joCell["apps"] = new JArray { joApp };
						}
						jaResult.Add(joCell);
					}
					else
					{
						JObject joApp = new()
						{
							["app_id"] = cell_row.Field<Int64>("app_id"),
							["app_name"] = cell_row.Field<string>("app_name")
						};

						(jtCell as JArray)!.Add(joApp);
					}
				}
			}

			jtResult = jaResult;

			return blRet;
		}

		/// <summary>
		/// Get Cell Type List
		/// "params":
		///		{}
		///	"result":
		///		[{
		///			"cell_type":"xxx",
		///			"cell_id":xxx
		///		},
		///		...
		///		]
		/// </summary>
		/// <param name="jtParam">params:{}</param>
		/// <param name="jtResult">result:[{"cell_type":"xxx","cell_id":xxx}, ...]</param>
		/// <returns>true</returns>
		[JsonRpcMethod]
		[JsonRpcMethodExample(@"{}")]
		static public bool GetCellTypeList(JToken jtParam, out JToken? jtResult)
		{
			Dictionary<Int64, string> dctCellTypes = CellService.Instance.GetCellTypeList();

			JObject joResult = new JObject();

			JArray jaCellTypeList = new JArray();
			foreach (var item in dctCellTypes)
			{
				jaCellTypeList.Add(new JObject()
				{
					["cell_type"] = item.Key,
					["cell_id"] = item.Value,
				});
			}
			joResult["cell_type_list"] = jaCellTypeList;
			jtResult = joResult;

			//jtResult = JArray.Parse(dctCellTypes.ToString()!);

			return true;
		}

		/// <summary>
		/// Get cell app list
		/// 1. with cell_type/cell_id, return cell app list with specified cell_type/cell_id
		/// 2. without cell_type/cell_id, return all cell app list grouped with cell id
		///		or
		///				"cell_app_list":{
		///					"cell_id_1":[{"app_name":cell_type, "app_id":app_id}, ...],
		///					...
		///				}
		///			}
		/// </summary>
		/// <param name="jtParam"></param>
		/// <param name="jtResult"></param>
		/// <returns></returns>
		[JsonRpcMethod]
		[JsonRpcMethodExample(@"{}")]
		[JsonRpcMethodExample(@"{""cell_type"":""cell type""}")]
		[JsonRpcMethodExample(@"{""cell_id"":1}")]
		static public bool GetCellAppList(JToken jtParam, out JToken? jtResult)
		{
			JObject joParam = (jtParam as JObject)!;

			Dictionary<Int64, string> dctCellApps;
			JObject joResult = new JObject();

			if (joParam!.TryGetValue("cell_type", out JToken? jtCellType)
				&& joParam.TryGetValue("cell_id", out JToken? jtCellID))
			{
				Int64 nCellID;

				if (jtCellType != null)
				{
					dctCellApps = CellService.Instance.GetCellAppList(jtCellType.ToString(), out nCellID);
				}
				else
				{
					nCellID = (Int64)jtCellID;
					dctCellApps = CellService.Instance.GetCellAppList(nCellID);
				}

				JArray jaCellIDs = new JArray();
				jaCellIDs.Add(nCellID);
				joResult["cell_id_list"] = jaCellIDs;

				JArray jaCellApps = new JArray();
				foreach (var item in dctCellApps)
				{
					jaCellApps.Add(new JObject()
					{
						["app_id"] = item.Key,
						["app_name"] = item.Value,
					});
				}

				joResult[nCellID.ToString()] = jaCellApps;
				//JObject joCellAppList = new JObject();
				//joCellAppList[nCellID.ToString()] = jaCellApps;

				//joResult["cell_app_list"] = jaCellApps;
			}
			else
			{	//Get All Cell app
				var dctCellIDs = CellService.Instance.GetCellAppList();

				JArray jaCellIDs = new JArray();
				JObject joCellApps = new JObject();
				foreach (var item in dctCellIDs)
				{
					jaCellIDs.Add(item.Key);
					JArray jaCellApps = new JArray();
					//jaCellApps.Merge(item.Value.ToArray());
					foreach (var item1 in item.Value)
					{
						JObject joApp = new JObject()
						{
							["app_id"] = item1.Key,
							["app_name"] = item1.Value,
						};
						jaCellApps.Add(joApp);
					}
					joResult[item.Key.ToString()] = jaCellApps;
				}

				joResult["cell_id_list"] = jaCellIDs;
			}

			jtResult = joResult;

			return true;
		}

		/// <summary>
		/// Create cell app with cell type and count
		/// params:
		///		{
		///			"cell_type":"cell_type",
		///			"count":xxx,
		///			"err_break":true
		///		}
		///	comment:
		///		errbreak default to true
		///	result:
		///		[{"app_name":"app_name1", "appid":xxx1}, {"app_name":"app_name2", "appid":xxx2}, ...]
		/// </summary>
		/// <param name="jtParam">{"cell_type":"xxx", "count":xxx}</param>
		/// <param name="jtResult">[{"app_name":"app_name1", "appid":xxx1}, {"app_name":"app_name2", "appid":xxx2}, ...]</param>
		/// <returns></returns>
		/// <exception cref="InvalidDataException"></exception>
		[JsonRpcMethod]
		[JsonRpcMethodExample(@"{""cell_type"":""cell_type"",""count"":1}")]
		[JsonRpcMethodExample(@"{""cell_type"":""cell_type"",""count"":1, ""err_break"":true}")]
		static public bool CreateCellApps(JToken jtParam, out JToken? jtResult)
		{
			bool blRet = false;

			JObject joParam = (jtParam as JObject)!;
			string? strCellType = joParam.Value<string>("cell_type");
			int nCount = joParam.Value<int>("count");
			if (strCellType == null || nCount <= 0)
				throw new InvalidDataException("Jsonrpc request parameters: cell_type or count was not to be set");

			bool blErrBreak = true;
			if (joParam.TryGetValue("errbreak", out JToken? jtErrBreak) == true)
				blErrBreak = (bool)jtErrBreak; 
			
			Int64 nCellID = CellService.Instance.GetCellID(strCellType);

			JArray jaResp = new();
			if (CellService.Instance.CreateCellApps(nCellID, nCount, blErrBreak, out DataTable ? dtAppIDs, out string? err_msg) == true)
			{
				foreach (DataRow item in dtAppIDs!.Rows)
				{
					JObject joAppInfo = new()
					{
						["app_id"] = item.Field<Int64>("app_id"),
						["app_name"] = item.Field<string>("app_name")
					};

					jaResp.Add(joAppInfo);
				}

				blRet = true;
			}
			else
			{
				blRet = false;
			}

			jtResult = jaResp;

			return blRet;
		}

		/// <summary>
		/// remove Cell app with appid array
		/// </summary>
		/// <param name="jtParam">{"cell_ids":[app_id_1, app_id_2, ...]}</param>
		/// <param name="jtResult">[{"app_id": app_id_xxx, "err_id":1, "err_msg":"xxx"}]</param>
		/// <returns></returns>
		[JsonRpcMethod]
		[JsonRpcMethodExample(@"{""cell_ids"":[1,2]}")]
		static public bool RemoveCellApps(JToken jtParam, out JToken? jtResult)
		{
			bool blRet = false;

			JObject joParam = (jtParam as JObject)!;
			if (joParam == null || joParam.Type != JTokenType.Object || joParam.TryGetValue("cell_ids", out JToken? jtCellIDs) || jtCellIDs == null)
				throw new ArgumentException($"Json params type is invalid");

			JArray jaResp = new();
			Int64[] app_ids = jtCellIDs.Values<Int64>().ToArray<Int64>();
			if (CellService.Instance.RemoveCellApps(app_ids, out JArray? jaResult) == false)
			{
				blRet = false;
			}

			jtResult = jaResp;

			return blRet;
		}

		/// <summary>
		/// Set Pointers value
		/// params:	{
		///		"ptrs":	[{
		///			"appid":appid,
		///			"pointers":	[
		///				"point",
		///				...
		///			},
		///			...
		///			],
		///			errbreak:true
		///		}
		///	result:
		///		[
		///			"appid":"pointer",
		///			...
		///		]
		/// </summary>
		/// <param name="jtParam"></param>
		/// <param name="jtResult"></param>
		/// <returns></returns>
		[JsonRpcMethod]
		[JsonRpcMethodExample(@"{}")]
		static public bool SetPointersValue(JToken jtParam, out JToken? jtResult)
		{
			JArray jaResult = new();

			bool blRet = CellService.Instance.SetPointersValue(jtParam, jaResult);
			jtResult = jaResult;

			return blRet;
		}

		/// <summary>
		/// Insert Cell Type
		/// params: {"cell_type":"cell_type", "cell_uuid":"cell_uuid", "force":true, }
		/// </summary>
		/// <param name="jtParam"></param>
		/// <param name="jtResult"></param>
		/// <returns></returns>
		/// <exception cref="InvalidDataException"></exception>
		/// <exception cref="ArgumentException"></exception>
		[JsonRpcMethod]
		[JsonRpcMethodExample(@"{""cell_type"":""cell type"", ""cell_uuid"":""cell uuid""}")]
		[JsonRpcMethodExample(@"{""cell_type"":""cell type"", ""cell_uuid"":""cell uuid"", ""force"":true}")]
		static public bool InstallCellType(JToken jtParam, out JToken? jtResult)
		{
			bool blRet = false;

			JObject joRequest = (jtParam as JObject)!;
			string? strCellType = joRequest.Value<string>("cell_type");
			string? strCellUUID = joRequest.Value<string>("cell_uuid");

			if (strCellType == null || strCellUUID == null)
				throw new InvalidDataException("Jsonrpc request parameters: cell_type or cell_uuid was not to be set");

			bool blForce = false;

			if (joRequest.ContainsKey("force"))
				blForce = joRequest.Value<bool>("force");

			Int64 cell_id;
			if (joRequest.TryGetValue("cell_id", out JToken? jtCellID))
				cell_id = (Int64)jtCellID;
			else // check the existance of cell_type, if exist, return the cell_id
				CellService.Instance.IsCellTypeExist(strCellType, strCellUUID, out cell_id);

			if (cell_id > 0)
			{
				CellService.Instance.LockCellType(cell_id);

				if (blForce == true)
				{
					CellService.Instance.RemoveCellType(cell_id, strCellUUID);

					//JObject joCellInfo = joRequest["cell_info"] as JObject;
					if (joRequest.TryGetValue("cell_info", out JToken? jtCellInfo))
					{
						CellService.Instance.InstallCellType(out cell_id, (jtCellInfo as JObject)!);
						CellService.Instance.LoadCellType(strCellType);
					}
					else
						throw new ArgumentException("Jsonrpc request parameters: cell_info was not to be set");
				}
				else
				{
					// update cell type
				}

				jtResult = true;
				blRet = true;
			}
			else
			{
				JObject joCellInfo = (joRequest["cell_info"] as JObject)!;

				CellService.Instance.InstallCellType(out cell_id, joCellInfo);
				CellService.Instance.LoadCellType(strCellType);

				jtResult = true;
				blRet = true;

			}

			return blRet;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="jtParam"></param>
		/// <param name="jtResult"></param>
		/// <returns></returns>
		/// <exception cref="InvalidDataException"></exception>
		//[JsonRpcMethod]
		/*static public bool IsCellTypeExist(JToken jtParam, out JToken? jtResult)
		{
			JObject joRequest = (jtParam as JObject)!;

			string? strCellType = joRequest.Value<string>("cell_type");
			string? strCellUUID = joRequest.Value<string>("cell_uuid");
			if (strCellType == null || strCellUUID == null)
				throw new InvalidDataException("Jsonrpc request parameters: cell_type or cell_uuid was not to be set");

			JObject joResult = new JObject();

			joResult["return"] = CellService.Instance.IsCellTypeExist(strCellType, strCellUUID, out Int64 cell_id);
			joResult["cell_id"] = cell_id;

			jtResult = joResult;

			return true;
		}*/

		/// <summary>
		/// {"cell_type":"cell_type", "cell_uuid":"cell_uuid"}
		/// </summary>
		/// <param name="jtParam"></param>
		/// <param name="jtResult"></param>
		/// <returns></returns>
		[JsonRpcMethod]
		[JsonRpcMethodExample(@"{""cell_type"":""cell_type"", ""cell_uuid"":""cell_uuid""}")]
		static public bool IsCellTypeExist(JToken jtParam, out JToken? jtResult)
		{
			bool blRet = false;

			JObject joRequest = (jtParam as JObject)!;
			string strCellType = joRequest.Value<string>("cell_type")!;
			string strCellUUID = joRequest.Value<string>("cell_uuid")!;
			if ((blRet = CellService.Instance.IsCellTypeExist(strCellType, strCellUUID, out Int64 cell_id)) == true)
			{
				JObject joResp = new JObject
				{
					["return"] = true,
					["cell_id"] = cell_id
				};
				jtResult = joResp;
			}
			else
			{
				JObject joResp = new JObject
				{
					["return"] = false
				};
				jtResult = joResp;
				blRet = true;
			}

			return blRet;
		}

		/// <summary>
		/// jtParams: {"user":"...", "passwd":"..."}
		/// jtResult: {"return":true/false, "timestamp":now }
		/// timestamp: the millisecond counts of linux time()
		/// </summary>
		/// <param name="jtParam"></param>
		/// <param name="jtResult"></param>
		/// <returns></returns>
		[JsonRpcMethod]
		[JsonRpcMethodExample(@"{""user"":""user_name"", ""passwd"":""password""}")]
		static public bool Login(JToken jtParam, out JToken? jtResult)
		{
			JObject joRequest = (jtParam as JObject)!;
			//if (joRequest == null)
			//	throw new NullReferenceException("null or invalid Jsonrpc request parameters");

			string? strUser = joRequest["user"]!.Value<string>();
			string? strPasswd = joRequest["passwd"]!.Value<string>();
			if (strUser == null || strPasswd == null)
				throw new InvalidDataException("Jsonrpc request parameters: user or passwd was not to be set");

			//blRet = CellService.Instance.IsCellTypeExist(strCellType, strCellUUID, out Int64 cell_id);

			JObject joResp = new JObject
			{
				["return"] = true,
				["timestamp"] = Utils.Utils.ConvertToTimestamp(DateTime.Now),
			};

			jtResult = joResp;

			return true;
		}
	}
}